package com.sunwayworld.basemodule.business.result.computing;

import com.sunwayworld.baseline.framework.cache.redis.LimsRedisHelper;
import com.sunwayworld.basemodule.business.result.bean.LimsResultBean;
import com.sunwayworld.basemodule.business.result.bean.LimsResultDTO;
import com.sunwayworld.basemodule.business.result.service.LimsResultService;
import com.sunwayworld.basemodule.common.utils.ConditionUtils;
import com.sunwayworld.basemodule.common.utils.GikamFormulaHelperUtils;
import com.sunwayworld.basemodule.coredata.judgmentbasis.bean.LimsJudgmentBasisTmAnalyteHighlowBean;
import com.sunwayworld.basemodule.coredata.judgmentbasis.service.LimsJudgmentBasisTmAnalyteHighlowService;
import com.sunwayworld.basemodule.coredata.test.bean.LimsPossibleResultBean;
import com.sunwayworld.basemodule.coredata.test.service.LimsPossibleResultService;
import com.sunwayworld.basemodule.samplemanage.sample.bean.LimsSampleBean;
import com.sunwayworld.basemodule.samplemanage.sample.service.LimsSampleService;
import com.sunwayworld.framework.beans.BeanPropertyEvent;
import com.sunwayworld.framework.beans.BeanPropertyListener;
import com.sunwayworld.framework.cache.redis.RedisHelper;
import com.sunwayworld.framework.context.ApplicationContextHelper;
import com.sunwayworld.framework.exception.InvalidDataException;
import com.sunwayworld.framework.exception.RoundingRuleException;
import com.sunwayworld.framework.formula.GikamComputingDataHolder;
import com.sunwayworld.framework.formula.GikamComputingEngine;
import com.sunwayworld.framework.formula.GikamFormulaRegistry;
import com.sunwayworld.framework.i18n.I18nHelper;
import com.sunwayworld.framework.mybatis.mapper.MapperParameter;
import com.sunwayworld.framework.mybatis.mapper.MatchPattern;
import com.sunwayworld.framework.mybatis.mapper.SearchFilter;
import com.sunwayworld.framework.script.BshExecutor;
import com.sunwayworld.framework.utils.*;
import com.sunwayworld.module.sys.rounding.bean.CoreRoundingRuleBean;
import com.sunwayworld.module.sys.rounding.service.CoreRoundingRuleService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class OrderTaskResultComputingEngine implements GikamComputingEngine {
    // 任务单下的结果集
    public static final String FOLDER_ORDER_TASK_RESULT_LIST = "FOLDER_ORDER_TASK_RESULT_LIST";
    // 任务单下的样本
    public static final String FOLDER_SAMPLE_LIST = "FOLDER_SAMPLE_LIST";
    //调用公式配置
    private List<String> removeFormulaRepository;

    // 判定依据结果集
    public static final String RESULT_JUDGE_LIST = "RESULT_JUDGE_LIST";
    public static final String POSSIBLE_RESULT_LIST = "POSSIBLE_RESULT_LIST";
    // 修约
    public static final String ROUNDING_RULE_LIST = "ROUNDING_RULE_LIST";

    private static final Logger logger = LogManager.getLogger(OrderTaskResultComputingEngine.class);

    private List<Long> folderIdList;

    private OrderTaskResultComputingEngine() {
    }

    public static OrderTaskResultComputingEngine getEngine(List<Long> folderIdList, String... removeFormulaRepository) {
        OrderTaskResultComputingEngine engine = new OrderTaskResultComputingEngine();
        engine.folderIdList = folderIdList;
        if (removeFormulaRepository.length > 0){
            engine.removeFormulaRepository = Arrays.asList(removeFormulaRepository);
        }else{
            engine.removeFormulaRepository = CollectionUtils.emptyList();
        }
        return engine;
    }

    public BshExecutor getBshExecutor() {
        BshExecutor bsh = BshExecutor.instance();

        bsh.eval("import com.sunwayworld.framework.utils.StringUtils;");
        bsh.eval("import com.sunwayworld.framework.utils.ArithUtils;");
        bsh.eval("import java.math.BigDecimal;");
        bsh.eval("import java.math.RoundingMode;");
        bsh.eval("import java.util.regex.Pattern;");

        bsh.eval("import java.text.*;");

        List<Class<?>> repository = GikamFormulaRegistry.INSTANCE.getRepository();
        for (Class clz:repository){
            if (!CollectionUtils.isEmpty(this.removeFormulaRepository) && this.removeFormulaRepository.contains(clz.getName())){
                continue;
            }
            bsh.eval("import static " + clz.getName() + ".*;");
        }

        return bsh;
    }

    public void sortData() {
    }

    public void processData() {
        LimsResultService taskResultService = ApplicationContextHelper.getBean(LimsResultService.class);
        List<LimsResultBean> updateTaskResultList = new ArrayList<>();
        // 获取更新的字段

        List<LimsResultDTO> orderTaskList = GikamComputingDataHolder.get(FOLDER_ORDER_TASK_RESULT_LIST);
        for (LimsResultDTO taskResult : orderTaskList) {
            BeanPropertyListener proxy = (BeanPropertyListener) taskResult;

            List<BeanPropertyEvent> propertyEventList = proxy.getChangedPropertyEventList().stream().filter(p -> !ObjectUtils.equals(p.getOldValue(), p.getNewValue())).collect(Collectors.toList());

            if (!propertyEventList.isEmpty()) {
                LimsResultBean resultBean = new LimsResultBean();
                resultBean.setId(taskResult.getId());
                resultBean.setOriginalResult(taskResult.getOriginalResult());
                resultBean.setFinalResult(taskResult.getFinalResult());
                resultBean.setStatus("done");
                resultBean.setJudge(taskResult.getJudge());
                updateTaskResultList.add(resultBean);
            }
        }

        if (!updateTaskResultList.isEmpty()) {
            taskResultService.getDao().fastUpdate(updateTaskResultList, "originalResult", "finalResult", "status", "judge");
        }
    }

    public void run(BshExecutor bsh) {
        boolean dependencyCheck = this.isDependencyCheck();
        if(dependencyCheck) return;

        List<LimsResultDTO> resultList = GikamComputingDataHolder.get(FOLDER_ORDER_TASK_RESULT_LIST);
        List<LimsJudgmentBasisTmAnalyteHighlowBean> highLowList = GikamComputingDataHolder.get(RESULT_JUDGE_LIST);
        List<LimsPossibleResultBean> possibleResultList = GikamComputingDataHolder.get(POSSIBLE_RESULT_LIST);
        List<CoreRoundingRuleBean> coreRoundingRuleBeans = GikamComputingDataHolder.get(ROUNDING_RULE_LIST);
        resultList.forEach(result -> {// 循环计算公式 -- 因为采用ThreadLocal 获取的数据
            // 公式计算原始结果
            if(!StringUtils.isEmpty(result.getFormula())) {
                GikamComputingDataHolder.setCurrentData(result);
                String script = GikamFormulaHelperUtils.getRevisedScript(result.getFormula());
                try {
                    if (!StringUtils.isEmpty(script)) {
                        Object resultValue = bsh.eval(script);
                        if (!ObjectUtils.isEmpty(resultValue)) {
                            result.setOriginalResult(resultValue.toString());
                        }
                    }
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                } finally {
                    GikamComputingDataHolder.removeCurrentData();
                }
            }
            // 修约计算最终结果
            String finalResult = result.getOriginalResult();
            String picture = result.getPicture();
            if (!StringUtils.isEmpty(finalResult) && !StringUtils.isEmpty(picture) && !StringUtils.equals(picture, "NONE")) {
                finalResult = rounding(finalResult, picture, bsh, coreRoundingRuleBeans);
            }
            result.setFinalResult(finalResult);
        });
        resultList.parallelStream().forEach(result -> {// 多线程进行结果判定
            if (!StringUtils.isEmpty(result.getFinalResult())) {
                //更新判定结果
                setResultJudge(result, highLowList, possibleResultList);
            }
        });
    }

    public static String rounding(String data, String roundingScheme, BshExecutor bsh, List<CoreRoundingRuleBean> coreRoundingRuleBeans) {
        if (StringUtils.isEmpty(data)) {
            return null;
        } else if (!NumberUtils.isNumber(data)) {
            throw new InvalidDataException("GIKAM.EXCEPTION.ROUNDING_NAN");
        } else if (!StringUtils.isEmpty(roundingScheme) && !"NONE".equals(roundingScheme) && !"无修约规则".equals(roundingScheme)) {
            String roundingRuleId = StringUtils.split(roundingScheme, ",")[0];
            CoreRoundingRuleBean roundingRule = coreRoundingRuleBeans.stream().filter(r -> r.getId().equalsIgnoreCase(roundingRuleId)).findFirst().orElse(null);
            if (roundingRule != null && !"1".equals(roundingRule.getLastSuspendedFlag())) {
                if (ObjectUtils.isEmpty(roundingRule.getScript())) {
                    throw new RoundingRuleException(roundingRule.getRuleName() + I18nHelper.getMessage("GIKAM.EXCEPTION.ROUNDINGRULE_SCRIPT_NOT_EXISTS", new String[0]));
                } else {
                    int numberCnt = NumberUtils.parseInt(StringUtils.split(roundingScheme, ",")[1]);
                    bsh.set("data", data);
                    bsh.set("numberCnt", numberCnt);
                    String result = bsh.eval(roundingRule.getScript());
                    return result;
                }
            } else {
                throw new RoundingRuleException("GIKAM.EXCEPTION.ROUNDINGRULE_EXISTS");
            }
        } else {
            return data;
        }
    }

    /**
     * 初始化计算公式所需的数据
     */
    public void initData() {
        LimsResultService resultService = ApplicationContextHelper.getBean(LimsResultService.class);
        LimsSampleService sampleService = ApplicationContextHelper.getBean(LimsSampleService.class);

        // 先根据folderIdList查询样品结果
        MapperParameter parameter = new MapperParameter();
        parameter.put("folderIds", StringUtils.join(this.folderIdList, ","));
        List<LimsResultDTO> folderTaskResultList = resultService.selectForFormula(parameter);

        List<Long> sampleIdList = folderTaskResultList.stream().map(LimsResultDTO::getSampleId).filter(Objects::nonNull)
                .distinct().collect(Collectors.toList());
        List<LimsSampleBean> sampleBeanList = sampleService.selectForFormula(sampleIdList);
        GikamComputingDataHolder.put(FOLDER_SAMPLE_LIST, sampleBeanList);


        List<LimsResultDTO> taskResultList = folderTaskResultList.stream().filter(e -> this.folderIdList.contains(e.getFolderId())).collect(Collectors.toList());

        // 获取FOLDER下代理类
        List<LimsResultDTO> taskResultProxyList = ObjectUtils.clone(taskResultList).stream().map(BeanUtils::getPropertyListenerProxy).collect(Collectors.toList());

        // 对应点位下的所有样品结果
        GikamComputingDataHolder.put(GikamComputingDataHolder.DEPENDENCY_CHECK_PREFIX + FOLDER_ORDER_TASK_RESULT_LIST, taskResultList);
        GikamComputingDataHolder.put(FOLDER_ORDER_TASK_RESULT_LIST, taskResultProxyList);

        // 涉及到的判定依据高低限集
        List<Long> specIdList = taskResultList.stream().map(LimsResultDTO::getJudgId).distinct().collect(Collectors.toList());
        LimsJudgmentBasisTmAnalyteHighlowService highLowService = ApplicationContextHelper.getBean(LimsJudgmentBasisTmAnalyteHighlowService.class);
        parameter.clear();
        parameter.setFilter(SearchFilter.instance().match("judgId", specIdList).filter(MatchPattern.OR));
        List<LimsJudgmentBasisTmAnalyteHighlowBean> analyteHighLowList = highLowService.selectHeighLowFullInfoList(parameter);
        GikamComputingDataHolder.put(RESULT_JUDGE_LIST, analyteHighLowList);
        
        // 获取备选结果，如果值等同于备选结果的其中一个，就同步其判定结果，优先级低于分析项的判定依据
        List<Long> testMethodIdList = taskResultList.stream().map(LimsResultDTO::getTestMethodId).distinct().collect(Collectors.toList());
        LimsPossibleResultService possibleResultService = ApplicationContextHelper.getBean(LimsPossibleResultService.class);
        List<LimsPossibleResultBean> possibleResultList = possibleResultService.selectListByFilter(SearchFilter.instance().match("testMethodId", testMethodIdList).filter(MatchPattern.OR));
        GikamComputingDataHolder.put(POSSIBLE_RESULT_LIST, possibleResultList);

        // 修约
        List<CoreRoundingRuleBean> coreRoundingRuleBeans = getRoundingRuleList();
        GikamComputingDataHolder.put(ROUNDING_RULE_LIST, coreRoundingRuleBeans);
    }

    private List<CoreRoundingRuleBean> getRoundingRuleList(){
        if(LimsRedisHelper.exists("ROUNDING_RULE_LIST", "RULE")) {
            return RedisHelper.get("ROUNDING_RULE_LIST", "RULE");
        } else {
            List<CoreRoundingRuleBean> coreRoundingRuleBeans = ApplicationContextHelper.getBean(CoreRoundingRuleService.class).selectAll();
            RedisHelper.put("ROUNDING_RULE_LIST", "RULE", coreRoundingRuleBeans);
            return coreRoundingRuleBeans;
        }
    }

    private void setResultJudge(LimsResultDTO result, List<LimsJudgmentBasisTmAnalyteHighlowBean> highLowList, List<LimsPossibleResultBean> possibleResultList) {
        LimsPossibleResultBean limsPossibleResultBean = possibleResultList.stream().filter(possibleResult -> ObjectUtils.equals(possibleResult.getAnalyteId(), result.getAnalyteId())
                && StringUtils.equals(possibleResult.getOriginalResult(), result.getFinalResult())).findAny().orElse(null);
        if (limsPossibleResultBean != null) {
            result.setJudge(limsPossibleResultBean.getJudge());
        } else {
            result.setJudge(null);
        }
        getResultJudge(result, highLowList);

    }

    private void getResultJudge(LimsResultDTO result, List<LimsJudgmentBasisTmAnalyteHighlowBean> highLowList) {
        LimsJudgmentBasisTmAnalyteHighlowBean highLowBean = highLowList.stream().filter(highLow -> {
            if (result.getJudgId().equals(highLow.getJudgId())
                    && StringUtils.equals(ObjectUtils.toString(result.getTestId()), highLow.getExt$Item("testId"))
                    && StringUtils.equals(ObjectUtils.toString(result.getTestMethodId()), highLow.getExt$Item("testMethodId"))
                    && StringUtils.equals(ObjectUtils.toString(result.getAnalyteId()), highLow.getExt$Item("analyteId"))
                    && ConditionUtils.compare(NumberUtils.parseDouble(result.getFinalResult()), highLow.getHigh())
                    && ConditionUtils.compare(NumberUtils.parseDouble(result.getFinalResult()), highLow.getLow())) {
                return true;
            }
            return false;
        }).findAny().orElse(null);

        if (highLowBean != null) {
            result.setJudge(highLowBean.getJudge());
        }
    }

}